import json
from uuid import uuid4

from django.core.exceptions import ObjectDoesNotExist
from django.core.files.base import ContentFile
from django.db import models
from django.utils import timezone

from sysreptor.pentests import querysets
from sysreptor.users.models import PentestUser
from sysreptor.utils.crypto.fields import EncryptedField
from sysreptor.utils.history import HistoricalRecords
from sysreptor.utils.models import BaseModel


class NoteType(models.TextChoices):
    TEXT = 'text', 'Text'
    EXCALIDRAW = 'excalidraw', 'Excalidraw'


class NotebookPageMixin(models.Model):
    note_id = models.UUIDField(default=uuid4, db_index=True, editable=False)
    type = models.CharField(max_length=32, default=NoteType.TEXT, editable=False)
    title = EncryptedField(base_field=models.TextField(default=''))
    text = EncryptedField(base_field=models.TextField(default=''))
    checked = models.BooleanField(null=True, blank=True)
    icon_emoji = models.CharField(max_length=32, null=True, blank=True)

    parent = models.ForeignKey(to='self', on_delete=models.CASCADE, null=True, blank=True)
    order = models.PositiveIntegerField()

    class Meta:
        abstract = True

    def __str__(self):
        return self.title

    def is_file_referenced(self, f) -> bool:
        return f.name in self.text or f.name in self.title

    def copy(self, **kwargs):
        return self.__class__.objects.copy(instance=self, **kwargs)

    @property
    def excalidraw_data(self) -> dict:
        if self.type != NoteType.EXCALIDRAW:
            return {}
        try:
            data = json.loads(self.excalidraw_file.file.read())
        except (ObjectDoesNotExist, FileNotFoundError, json.JSONDecodeError):
            return {}
        if not isinstance(data, dict) or not isinstance(data.get('elements'), list):
            return {}
        return {'elements': data.get('elements', [])}

    def update_excalidraw_data(self, excalidraw_data: dict):
        from sysreptor.pentests.consumers import send_collab_event_excalidraw
        from sysreptor.pentests.models import CollabEvent, CollabEventType
        from sysreptor.pentests.models.collab import collab_context_store

        if self.type != NoteType.EXCALIDRAW:
            raise ValueError(f'Cannot set excalidraw_data. note.type is not {NoteType.EXCALIDRAW}')
        if not isinstance(excalidraw_data, dict) or not isinstance(excalidraw_data.get('elements', []), list):
            raise ValueError('Invalid excalidraw data')
        if self.excalidraw_data != excalidraw_data:
            file = ContentFile(content=json.dumps(excalidraw_data).encode(), name=f'excalidraw-{timezone.now().isoformat()}.json')
            self._meta.model.excalidraw_file.related.related_model.objects.update_or_create(
                linked_object=self,
                defaults={'file': file},
                create_defaults={'file': file},
            )
            if not getattr(collab_context_store, 'prevent_events', False):
                timestamp = timezone.now()
                send_collab_event_excalidraw(CollabEvent.objects.create(
                    type=CollabEventType.UPDATE_EXCALIDRAW,
                    related_id=self.id,
                    path='excalidraw',
                    created=timestamp,
                    version=timestamp.timestamp(),
                    data={
                        'elements': excalidraw_data.get('elements', []),
                        'sync_all': True,
                    },
                ))


class ProjectNotebookPage(NotebookPageMixin, BaseModel):
    assignee = models.ForeignKey(to=PentestUser, on_delete=models.SET_NULL, null=True, blank=True)
    project = models.ForeignKey(to='PentestProject', on_delete=models.CASCADE, related_name='notes')

    history = HistoricalRecords()
    objects = querysets.ProjectNotebookPageManager()

    class Meta:
        unique_together = [('project', 'note_id')]


class UserNotebookPage(NotebookPageMixin, BaseModel):
    user = models.ForeignKey(to=PentestUser, on_delete=models.CASCADE, related_name='notes')

    objects = querysets.UserNotebookPageManager()

    class Meta:
        unique_together = [('user', 'note_id')]


class ShareInfo(BaseModel):
    note = models.ForeignKey(to=ProjectNotebookPage, on_delete=models.CASCADE, related_name='shareinfos')
    expire_date = models.DateField(db_index=True)
    is_revoked = models.BooleanField(default=False, db_index=True)
    password = EncryptedField(base_field=models.CharField(max_length=255, null=True, blank=True), null=True, blank=True)
    permissions_write = models.BooleanField(default=False)
    shared_by = models.ForeignKey(to=PentestUser, on_delete=models.SET_NULL, null=True, blank=True)
    comment = models.TextField(null=True, blank=True)

    failed_password_attempts = models.PositiveIntegerField(default=0)

    objects = querysets.ShareInfoManager()

    @property
    def is_active(self) -> bool:
        return self.expire_date >= timezone.now().date() and not self.is_revoked

